这里是Z哥的个人公众号
每周五11:45 按时送达
当然了,也会时不时加个餐~
我的第「197」篇原创敬上
不知道在看这篇文章的程序员伙计们平时是如何写代码的?有参照什么原则吗?还是说写到哪算哪?如果你在搜索引擎里搜一下「软件开发原则」,可以搜到N多种原则,每一个原则看上去都很有道理,很对。下图就是我之前整理的一个与设计原则相关的思维导图。
但是不管你整理的多好,很多人到实际写代码的时候完全想不起这些原则。不用自我怀疑,大多数人都是如此,你并不是特例。
之所以会有这样的情况,是因为总结后的原则大多都太抽象了,往往只有一句话,甚至只是一个词,自然不会有太多深刻的印象。我们今天不聊这些刻板的名词,来聊聊Z哥在工作中常用到的一些“原则”,以及它们的适用场景,帮助你更好地记住它们。另外,我还给它们做了一下分类,更便于你记忆。这个原则不管是在单体应用,还是分布式应用里都是非常重要的一个原则,它可以避免「big ball of mud」项目的产生。而且,如果项目里存在着过多的循环依赖,也更容易一不小心写出循环调用的代码,让整个系统陷入死循环。在满足「01」的前提下,尽量做到单向依赖可以大大降低阅读代码、排查问题时的复杂度。如果实在对上游有依赖的话,尽量通过IOC的思路来处理,用隐性依赖代替显性依赖。如果实在没法通过IOC来解决的话,可以将依赖上游的数据在当前系统冗余一份,然后通过MQ来保持数据同步,在业务处理的时候直接使用本地的这份冗余数据。当然,这个方法的复杂度明显比上面的更高,所以还是优先考虑上面的方案。在满足「1」和「2」的前提下,尽量做到避免跨层调用,可以很起到更好的封装效果。举个最简单的反例,就拿三层架构来说,如果应用层的代码可以直接访问数据访问层,那么业务逻辑层自然会形同虚设。而且,后续一旦涉及到某数据表增加一个参数,要修改的相关调用代码可多了……这也是为什么很多维护不善的老项目越往后大家就越不敢乱动代码的主要原因之一。其实我在后面会提到SOLID原则,这里为什么将单一原则单独拿出来说呢,因为我觉得它是SOLID的六大原则里最重要的,虽然它看上去最简单。单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分。Robert C. Martin《敏捷软件开发:原则、模式和实践》
只有深刻理解这个概念,你才能真正发挥面向对象编程语言的最大优势。并且,这个思路也可以运用在模块的划分上。遵循这一原则最关键的地方在于职责的划分,很多人其实并没有掌握好正确的划分思路。因为这个的确很难,需要你对业务有深入的了解,因为职责存在于业务里。比如,在电商系统里体现「一个商品在某个平台销售」这个业务,你可以既在「商品」类上设置「销售渠道」属性,也可以在「销售渠道」上设置「在售商品列表」属性,还可以单独设计一个「商品绑定销售渠道」的类。但是我们从单一职责原则来考虑的话,就应该选择最后一个方案。为什么呢?因为在不同的渠道销售商品,其实对商品和销售渠道本身都没有什么影响,商品还是那个商品、渠道还是那个渠道,因此这个业务不是它们的职责。这一点可能算不上传统意义上的原则吧。但是我觉得这是很容易体现开发水平高低的一点。所以也列了一下。大部分的 if-else 都可以合理运用设计模式来消灭掉。比如, 状态模式、策略模式、命令模式、责任链模式、代理模式。如果对这些设计模式的形态有些模糊了,那么赶紧去回顾一下。冗余数据的确可以带来很多便利,比如减少RPC请求查询其它程序内的数据。但是副作用也是很明显的,付出了需要解决数据一致性问题为代价。因此仅当存在性能要求时,才考虑数据冗余。 在平时的代码设计中,你可以有很多方法来降低不必要的数据冗余,比如:给每一个API或者Function区分必要参数和可选参数。如此一来,对调用方来说能够减少为了传入可选参数而做的不必要的数据冗余以及RPC请求。
如果是会对外提供访问的API,一定要最小化参数,可以自行获取的数据尽量在内部自行获取,不要求外部传入。目的同1。
我觉得能意识到上面的这些设计原则,已经算得上是一个合格的程序员了。如果想要更近一步,还可以在以下这几个方面考虑。这个原则鼎鼎大名了,应该大家都知道,就不展开说了。Single Responsibility Principle:单一职责原则
Open Closed Principle:开闭原则
Liskov Substitution Principle:里氏替换原则
Law of Demeter:迪米特法则
Interface Segregation Principle:接口隔离原则
Dependence Inversion Principle:依赖倒置原则
我为什么将它们放到进阶里面呢,因为我觉得这里面除了单一职责,其它几个原则还兼顾着在可扩展性上的考量。所以,除了单一职责以外的原则没做到位,最多牺牲了可扩展性和一定的耦合度。但是单一职责没做好,可会存在非常大的耦合问题。这点可能在单体应用中感受不明显。但是在分布式系统却重要得多。因为网络是不可靠的,如果设计的代码不可重试,那么会存在大量的数据不一致问题需要手动去处理。可头疼死你。重视「幂等」的原因和「可重试」一样,在单体应用中作用不大,最多对瞬时的重复点击有作用。但是在不可靠网络的分布式系统中,某个请求被重复提交的可能性大大增加,如何保证多次请求的结果是一致的就至关重要了。前面的「可重试」和「幂等」更多是在代码级别的数据准确性设计。如果在整个大系统层面考虑数据准确性,需要基于经典的CAP定理、BASE理论去设计。什么业务场景需要保证强一致性,什么业务场景可以接受存在延迟的最终一致性,是需要仔细考量的。多提一句,如果采用最终一致性方案的话,尽可能地增加一个后续的核对机制,以解决某些异步消息在中途丢失、长期异常挂起等等导致的数据不一致问题。其实,要在代码设计上考虑数据安全,只需要一些非常基础的业务意识就够了。你只要能识别到哪些数据是敏感的,针对这些数据做一些保护机制,防止数据泄漏。比如,加密、脱敏、避免越权、减少非必要传输等等。以上的这些是我目前暂时想到的在工作中最常用的开发原则。如果后续再想到什么我会补充在评论区,也欢迎你在评论区发表你的经验之谈。还是总结一下,这篇呢Z哥与你分享了一些我在工作中常用的开发原则。总体来说,他们分为4类。耦合:避免循环依赖、尽量单向依赖、避免跨层调用。
对象设计:单一职责原则、减少if else、数据冗余、SOLID原则。
数据准确性:可重试、幂等、CAP、BASE。
数据存储:数据安全。
“大道理都懂,但还是过不好这一生”。这句话也能适用在这里,“开发原则都懂,但发现眼前的项目还是如此不堪”。之所以如此,我觉得是因为很多时候,我们假装没看到项目里的代码坏味道,认为与自己没啥关系,孰不知,它很有可能在不久的将来给你一击“反噬”,让你不得不硬着头皮面对它。
推荐阅读:
原创不易,如果你觉得这篇文章还不错,就「点赞」或者「在看」一下吧,鼓励我的创作 :)
也可以分享我的公众号名片给有需要的朋友们。
如果你有关于软件架构、分布式系统、产品、运营的困惑
可以试试点击「阅读原文」